package com.yuandian.management.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yuandian.api.management.entity.ConfMpChannel;
import com.yuandian.api.management.po.ConfMpChannelParams;
import com.yuandian.api.management.vo.ConfMpChannelExcelVo;
import com.yuandian.bpm.common.core.util.R;
import com.yuandian.bpm.common.excel.vo.ErrorMessage;
import com.yuandian.management.mapper.ConfMpChannelMapper;
import com.yuandian.management.service.ConfMpChannelService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.BindingResult;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 中台监控-渠道配置表-Service实现
 * @author zfsn
 * @date 2023-4-26
 */
@Service
public class ConfMpChannelServiceImpl extends ServiceImpl<ConfMpChannelMapper, ConfMpChannel>
		implements ConfMpChannelService {

	/**
	 * 根据渠道编码获取一条渠道配置数据
	 * @param channelCode 渠道编码
	 * @return 渠道配置实体
	 */
	private ConfMpChannel getByChannelCode(String channelCode) {
		return lambdaQuery().eq(ConfMpChannel::getChannelCode, channelCode).one();
	}

	/**
	 * 分页查询
	 * @param params 中台监控-渠道配置请求参数
	 * @return 通用返回对象
	 */
	@Override
	public R<Page<ConfMpChannel>> page(ConfMpChannelParams params) {
		Page<ConfMpChannel> page = Page.of(params.getCurrent(), params.getSize());
		page.addOrder(params.orders()).orders().forEach(orderItem -> orderItem.setColumn(StrUtil.toUnderlineCase(orderItem.getColumn())));
		return R.ok(lambdaQuery()
				.in(CollUtil.isNotEmpty(params.getChannelCodeList()), ConfMpChannel::getChannelCode, params.getChannelCodeList())
				.in(CollUtil.isNotEmpty(params.getInternalOrgCodeList()), ConfMpChannel::getInternalOrgCode, params.getInternalOrgCodeList())
				.like(StrUtil.isNotEmpty(params.getChannelName()), ConfMpChannel::getChannelName, params.getChannelName())
				.page(page));
	}

	/**
	 * 新增数据
	 * @param params 中台监控-渠道配置请求参数
	 * @return 通用返回对象
	 */
	@Transactional(rollbackFor = Exception.class)
	@Override
	public R<Boolean> saveWrap(ConfMpChannelParams params) {
		ConfMpChannel confMpChannel = BeanUtil.copyProperties(params, ConfMpChannel.class);
		if (ObjectUtil.isNotNull(getByChannelCode(confMpChannel.getChannelCode()))) {
			return R.failed("新增失败，已存在相同的渠道编码！");
		}
		save(confMpChannel);
		return R.ok(Boolean.TRUE, "新增成功");
	}

	/**
	 * 通过主键更新数据
	 * @param params 中台监控-渠道配置请求参数
	 * @return 通用返回对象
	 */
	@Transactional(rollbackFor = Exception.class)
	@Override
	public R<Boolean> updateByIdWrap(ConfMpChannelParams params) {
		// 1、校验请求参数
		Integer id = params.getId();
		if (id == null) {
			return R.failed("更新失败，渠道id不能为空！");
		}
		// 2、通过id获取渠道配置
		ConfMpChannel channelById = getById(id);
		// 3、校验该配置是否存在
		if (ObjectUtil.isNull(channelById)) {
			return R.failed("更新失败，不存在该渠道配置！");
		}
		// 4、校验渠道编码的唯一性
		ConfMpChannel channelByCode = getByChannelCode(params.getChannelCode());
		if (ObjectUtil.isNotNull(channelByCode) && !id.equals(channelByCode.getId())) {
			return R.failed("更新失败，已存在相同的渠道编码！");
		}
		// 5、更新渠道配置
		BeanUtil.copyProperties(params, channelById);
		updateById(channelById);
		return R.ok(Boolean.TRUE, "更新成功");
	}

	/**
	 * 通过Excel导入渠道配置
	 * @param excelVoList 中台监控-渠道配置Excel vo对象集合
	 * @param bindingResult 用来校验 实体 jsr303 校验失败结果
	 * @return 通用返回对象
	 */
	@Transactional(rollbackFor = Exception.class)
	@Override
	public R<List<ErrorMessage>> importChannel(List<ConfMpChannelExcelVo> excelVoList, BindingResult bindingResult) {
		// 1、校验请求参数
		if (CollUtil.isEmpty(excelVoList)) {
			return R.failed("导入失败，Excel为空或解析失败！");
		}
		// 2、获取渠道配置唯一性校验集
		Map<String, Integer> uniqueChannelMap = lambdaQuery()
				.select(ConfMpChannel::getId, ConfMpChannel::getChannelCode)
				.list()
				.stream()
				.collect(Collectors.toMap(ConfMpChannel::getChannelCode, ConfMpChannel::getId, (k1, k2) -> k1));
		// 3、遍历导入的数据集
		long failCount = 0;
		List<ErrorMessage> errorMessageList = (List<ErrorMessage>) bindingResult.getTarget();
		assert errorMessageList != null;
		Map<String, ConfMpChannel> map = new HashMap<>();
		for (ConfMpChannelExcelVo channel : excelVoList) {
			Set<String> errorMsg = new HashSet<>();
			String channelCode = channel.getChannelCode();
			String channelName = channel.getChannelName();
			String internalOrgCode = channel.getInternalOrgCode();
			// 非空校验
			if (!StrUtil.isAllNotBlank(channelCode, channelName, internalOrgCode)) {
				errorMsg.add("非空校验不通过，请检查以下字段：渠道名称、渠道编码、映射关系");
				errorMessageList.add(new ErrorMessage(channel.getLineNum(), errorMsg));
				failCount++;
				continue;
			}
			ConfMpChannel.ConfMpChannelBuilder builder = ConfMpChannel.builder();
			// 唯一校验
			Integer id = uniqueChannelMap.get(channelCode);
			if (id != null) {
				// 存在重复的编码，处理方式是覆盖更新，所以这里创建实体对象时需要回填id
				builder.id(id);
			}
			// 通过校验，创建实体对象添加到待入库集合中
			map.put(channelCode, builder
					.channelCode(channelCode)
					.channelName(channelName)
					.internalOrgCode(internalOrgCode)
					.build());
		}
		// 4、批量写入渠道配置表
		Collection<ConfMpChannel> list = map.values();
		if (CollUtil.isNotEmpty(list)) {
			saveOrUpdateBatch(list, 1000);
		}
		return R.ok(errorMessageList, "导入完成，失败条数：" + failCount);
	}
}